/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wdullaer.materialdatetimepicker.date;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.global.resource.ResourceManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.TouchEvent;

import com.wdullaer.materialdatetimepicker.JalaliCalendar;
import com.wdullaer.materialdatetimepicker.ResourceTable;
import com.wdullaer.materialdatetimepicker.Utils;
import com.wdullaer.materialdatetimepicker.common.ResourceUtils;
import com.wdullaer.materialdatetimepicker.date.MonthAdapter.CalendarDay;

import java.security.InvalidParameterException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

/**
 * A calendar-like view displaying a specified month and the appropriate selectable day numbers
 * within the specified month.
 *
 * @date 2021/07/30
 */
public abstract class MonthView extends Component {

    private static final String TAG = "MonthFragment";
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 1234567, TAG);

    protected static final int DEFAULT_HEIGHT = 32;
    protected static final int DEFAULT_WIDTH = 762;
    protected static final int DEFAULT_SELECTED_DAY = -1;
    protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
    protected static final int DEFAULT_NUM_DAYS = 7;
    protected static final int DEFAULT_NUM_ROWS = 6;
    protected static final int MAX_NUM_ROWS = 6;

    private static final int SELECTED_CIRCLE_ALPHA = 255;

    protected static final int DAY_SEPARATOR_WIDTH = 1;
    protected int MINI_DAY_NUMBER_TEXT_SIZE;
    protected int MONTH_LABEL_TEXT_SIZE;
    protected int MONTH_DAY_LABEL_TEXT_SIZE;
    protected int MONTH_HEADER_SIZE;
    protected int MONTH_HEADER_SIZE_V2;
    protected int DAY_SELECTED_CIRCLE_SIZE;
    protected int DAY_HIGHLIGHT_CIRCLE_SIZE;
    protected int DAY_HIGHLIGHT_CIRCLE_MARGIN;
    protected Font font;

    protected DatePickerController mController;

    // affects the padding on the sides of this view
    protected int mEdgePadding = 0;

    private String mDayOfWeekTypeface;
    private String mMonthTitleTypeface;

    protected Paint mMonthNumPaint;
    protected Paint mMonthTitlePaint;
    protected Paint mSelectedCirclePaint;
    protected Paint mMonthDayLabelPaint;

    private final StringBuilder mStringBuilder;

    protected int mMonth;

    protected int mYear;
    // Quick reference to the width of this view, matches parent
    protected int mWidth = DEFAULT_WIDTH;
    // The height this view should draw at in pixels, set by height param
    protected int mRowHeight = DEFAULT_HEIGHT;
    // If this view contains the today
    protected boolean mHasToday = false;
    // Which day is selected [0-6] or -1 if no day is selected
    protected int mSelectedDay = -1;
    // Which day is today [0-6] or -1 if no day is today
    protected int mToday = DEFAULT_SELECTED_DAY;
    // Which day of the week to start on [0-6]
    protected int mWeekStart = DEFAULT_WEEK_START;
    // How many days to display
    protected int mNumDays = DEFAULT_NUM_DAYS;
    // The number of days + a spot for week number if it is displayed
    protected int mNumCells = mNumDays;

    private Calendar mCalendar;
    protected Calendar mDayLabelCalendar;
    protected int mNumRows = DEFAULT_NUM_ROWS;

    // Optional listener for handling day click actions
    protected OnDayClickListener mOnDayClickListener;

    // Whether to prevent setting the accessibility delegate
    protected Color mDayTextColor;
    protected Color mSelectedDayTextColor;
    protected Color mMonthDayTextColor;
    protected Color mTodayNumberColor;
    protected Color mHighlightedDayTextColor;
    protected Color mDisabledDayTextColor;
    protected Color mMonthTitleColor;

    private SimpleDateFormat weekDayLabelFormatter;

    public MonthView(Context context, AttrSet attr, DatePickerController controller, Font font) {
        super(context, attr);
        this.font = font;
        mController = controller;
        ResourceManager res = context.getResourceManager();

        if (controller != null) {  // TODO // 处理FIndbugs：mController、mDayLabelCalendar去掉final
            switch (controller.getCalendarType()) {
                case JALALI:
                    mDayLabelCalendar = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale());
                    mCalendar = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale());
                    break;
                default:
                    mDayLabelCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale());
                    mCalendar = Calendar.getInstance(mController.getTimeZone(), mController.getLocale());
                    break;
            }
        }


        mDayOfWeekTypeface = ResourceUtils.getString(res, ResourceTable.String_mdtp_day_of_week_label_typeface);
        mMonthTitleTypeface = ResourceUtils.getString(res, ResourceTable.String_mdtp_sans_serif);
        boolean darkTheme = mController != null && mController.isThemeDark();
        if (darkTheme) {
            mDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_normal_dark_theme);
            mMonthDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_month_day_dark_theme);
            mDisabledDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_disabled_dark_theme);
            mHighlightedDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_highlighted_dark_theme);
        } else {
            mDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_normal);
            mMonthDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_month_day);
            mDisabledDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_disabled);
            mHighlightedDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_date_picker_text_highlighted);
        }
        mSelectedDayTextColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_white);
        mTodayNumberColor = new Color(mController.getAccentColor());
        mMonthTitleColor = ResourceUtils.getColor(res, ResourceTable.Color_mdtp_white);

        mStringBuilder = new StringBuilder(50);

        MINI_DAY_NUMBER_TEXT_SIZE = ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_day_number_size);
        MONTH_LABEL_TEXT_SIZE = ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_month_label_size);
        MONTH_DAY_LABEL_TEXT_SIZE = ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_month_day_label_text_size);
        MONTH_HEADER_SIZE = ResourceUtils.getDimensionPixelOffset(context, ResourceTable.Float_mdtp_month_list_item_header_height);
        MONTH_HEADER_SIZE_V2 = ResourceUtils.getDimensionPixelOffset(context, ResourceTable.Float_mdtp_month_list_item_header_height_v2);
        DAY_SELECTED_CIRCLE_SIZE = mController.getVersion() == DatePickerDialog.Version.VERSION_1
            ? ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_day_number_select_circle_radius)
            : ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_day_number_select_circle_radius_v2);
        DAY_HIGHLIGHT_CIRCLE_SIZE = ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_day_highlight_circle_radius);
        DAY_HIGHLIGHT_CIRCLE_MARGIN = ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_day_highlight_circle_margin);

        if (mController.getVersion() == DatePickerDialog.Version.VERSION_1) {
            mRowHeight = (ResourceUtils.getDimensionPixelOffset(context, ResourceTable.Float_mdtp_date_picker_view_animator_height)
                - getMonthHeaderSize()) / MAX_NUM_ROWS;
        } else {
            mRowHeight = (ResourceUtils.getDimensionPixelOffset(context, ResourceTable.Float_mdtp_date_picker_view_animator_height_v2)
                - getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE * 2) / MAX_NUM_ROWS;
        }

        mEdgePadding = mController.getVersion() == DatePickerDialog.Version.VERSION_1
            ? 0
            : ResourceUtils.getDimensionPixelSize(context, ResourceTable.Float_mdtp_date_picker_view_animator_padding_v2);

        // Sets up any standard paints that will be used
        initView();

        setLayoutRefreshedListener(layoutRefreshedListener);
        setTouchEventListener(mTouchEventListener);
        addDrawTask(mDrawTask);
    }

    public void setOnDayClickListener(OnDayClickListener listener) {
        mOnDayClickListener = listener;
    }

    TouchEventListener mTouchEventListener = new TouchEventListener() {
        //        private int pointId;
        private boolean canceled = true;
        private float downX;
        private float downY;

        @Override
        public boolean onTouchEvent(Component component, TouchEvent event) {
            Utils.log("MonthView onTouchEvent action:" + event.getAction() + ", index:" + event.getIndex() + ", pointerId:" + event.getPointerId(0));
            switch (event.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    canceled = false;
                    downX = event.getPointerPosition(0).getX();
                    downY = event.getPointerPosition(0).getY();
                    break;
                case TouchEvent.POINT_MOVE:
                    if (event.getPointerPosition(0).getX() - downX >= 20 || event.getPointerPosition(0).getY() - downY >= 20) {
                        canceled = true;
                    }
                    break;
                case TouchEvent.CANCEL:
                    canceled = true;
                    break;
                case TouchEvent.PRIMARY_POINT_UP:
                    if (canceled) {
                        return false;
                    }
                    final int day = getDayFromLocation(event.getPointerPosition(0).getX(), event.getPointerPosition(0).getY());
                    Utils.log("MonthView onTouchEvent day:" + day);
                    if (day >= 0) {
                        onDayClick(day);
                    }
                    break;
            }
            return true;
        }
    };

    /**
     * Sets up the text and style properties for painting. Override this if you
     * want to use a different paint.
     */
    protected void initView() {
        mMonthTitlePaint = new Paint();
        if (mController.getVersion() == DatePickerDialog.Version.VERSION_1)
            mMonthTitlePaint.setFakeBoldText(true);
        mMonthTitlePaint.setAntiAlias(true);
        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
        mMonthTitlePaint.setFont(new Font.Builder(mMonthTitleTypeface).setWeight(Font.BOLD).build());
        mMonthTitlePaint.setColor(mDayTextColor);
        mMonthTitlePaint.setTextAlign(TextAlignment.CENTER);
        mMonthTitlePaint.setStyle(Paint.Style.FILL_STYLE);

        mSelectedCirclePaint = new Paint();
        mSelectedCirclePaint.setFakeBoldText(true);
        mSelectedCirclePaint.setAntiAlias(true);
        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA / 255f);
        mSelectedCirclePaint.setColor(mTodayNumberColor);
        mSelectedCirclePaint.setTextAlign(TextAlignment.CENTER);
        mSelectedCirclePaint.setStyle(Paint.Style.FILL_STYLE);

        mMonthDayLabelPaint = new Paint();
        mMonthDayLabelPaint.setAntiAlias(true);
        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
        mMonthDayLabelPaint.setColor(mMonthDayTextColor);
        mMonthTitlePaint.setFont(new Font.Builder(mDayOfWeekTypeface).setWeight(Font.BOLD).build());
        mMonthDayLabelPaint.setStyle(Paint.Style.FILL_STYLE);
        mMonthDayLabelPaint.setTextAlign(TextAlignment.CENTER);
        mMonthDayLabelPaint.setFakeBoldText(true);

        mMonthNumPaint = new Paint();
        mMonthNumPaint.setAntiAlias(true);
        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
        mMonthNumPaint.setStyle(Paint.Style.FILL_STYLE);
        mMonthNumPaint.setTextAlign(TextAlignment.CENTER);
        mMonthNumPaint.setFakeBoldText(false);
    }

    DrawTask mDrawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            drawMonthTitle(canvas);
            drawMonthDayLabels(canvas);
            drawMonthNums(canvas);
        }
    };

    private int mDayOfWeekStart = 0;

    /**
     * 月设置参数
     * Sets all the parameters for displaying this week. The only required
     * parameter is the week number. Other parameters have a default value and
     * will only update if a new value is included, except for focus month,
     * which will always default to no focus month if no value is passed in.
     *
     * @param selectedDay 选择一天
     * @param year 一年
     * @param month 月
     * @param weekStart 周开始
     */
    public void setMonthParams(int selectedDay, int year, int month, int weekStart) {
        if (month == -1 && year == -1) {
            throw new InvalidParameterException("You must specify month and year for this view");
        }

        mSelectedDay = selectedDay;

        // Allocate space for caching the day numbers and focus values
        mMonth = month;
        mYear = year;

        Calendar today;
        switch (mController.getCalendarType()) {
            case JALALI:
                today = JalaliCalendar.getInstance(mController.getTimeZone(), mController.getLocale());
                break;
            default:
                today = Calendar.getInstance(mController.getTimeZone(), mController.getLocale());
                break;
        }
        Utils.log("MonthView setMonthParams selectedDay:" + selectedDay + ", year:" + year + ", month:" + month + ", weekStart:" + weekStart + " --- today:" + Utils.getCanlendarStr(today));
        mHasToday = false;
        mToday = -1;

        mCalendar.set(Calendar.MONTH, mMonth);
        mCalendar.set(Calendar.YEAR, mYear);
        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);

        if (weekStart != -1) {
            mWeekStart = weekStart;
        } else {
            mWeekStart = mCalendar.getFirstDayOfWeek();
        }

        mNumCells = mCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        for (int i = 0; i < mNumCells; i++) {
            final int day = i + 1;
            if (sameDay(day, today)) {
                mHasToday = true;
                mToday = day;
            }
        }
        mNumRows = calculateNumRows();

    }

    @SuppressWarnings("unused")
    public void setSelectedDay(int day) {
        Utils.log("MonthView setSelectedDay day:" + day);
        mSelectedDay = day;
    }

    public int getSelectedDay() {
        return mSelectedDay;
    }

    private int calculateNumRows() {
        int offset = findDayOffset();
        int dividend = (offset + mNumCells) / mNumDays;
        int remainder = (offset + mNumCells) % mNumDays;
        return (dividend + (remainder > 0 ? 1 : 0));
    }

    private boolean sameDay(int day, Calendar today) {
        return mYear == today.get(Calendar.YEAR) &&
            mMonth == today.get(Calendar.MONTH) &&
            day == today.get(Calendar.DAY_OF_MONTH);
    }

    LayoutRefreshedListener layoutRefreshedListener = new LayoutRefreshedListener() {
        @Override
        public void onRefreshed(Component component) {
            HiLog.warn(LABEL, "### LayoutRefreshedListener onRefreshed");
            mWidth = component.getWidth();

            int height = component.getHeight();
            int expectedHeight = mRowHeight * mNumRows + getMonthHeaderSize();
            if (height != expectedHeight) {
                setHeight(expectedHeight);
            }
        }
    };

    public int getMonth() {
        return mMonth;
    }

    public int getYear() {
        return mYear;
    }

    /**
     * 获取月份高度
     *
     * @return The height in pixels of a row of day labels
     */
    public int getMonthHeight() {
        int scaleFactor = mController.getVersion() == DatePickerDialog.Version.VERSION_1 ? 2 : 3;
        return getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE * scaleFactor;
    }

    /**
     * 获取单元格宽度
     *
     * @return The width in pixels of a day label
     */
    public int getCellWidth() {
        return (mWidth - mEdgePadding * 2) / mNumDays;
    }

    /**
     * 获取边缘填充
     *
     * @return The left / right padding used when calculating day number positions
     */
    public int getEdgePadding() {
        return mEdgePadding;
    }

    /**
     * 得到月头大小
     * A wrapper to the MonthHeaderSize to allow override it in children
     *
     * @return int
     */
    protected int getMonthHeaderSize() {
        return mController.getVersion() == DatePickerDialog.Version.VERSION_1
            ? MONTH_HEADER_SIZE
            : MONTH_HEADER_SIZE_V2;
    }

    /*@NonNull*/
    private String getMonthAndYearString() {
        if (mController.getCalendarType() == DatePickerDialog.Type.JALALI) {
            return ((JalaliCalendar) mCalendar).getMonthName() + " " + ((JalaliCalendar) mCalendar).get(Calendar.YEAR);
        }
        Locale locale = mController.getLocale();
        String pattern = ResourceUtils.getString(getResourceManager(), ResourceTable.String_mdtp_date_v1_monthyear);

        SimpleDateFormat formatter = new SimpleDateFormat(pattern, locale);
        formatter.setTimeZone(mController.getTimeZone());
        formatter.applyLocalizedPattern(pattern);
        mStringBuilder.setLength(0);
        return formatter.format(mCalendar.getTime());
    }

    protected void drawMonthTitle(Canvas canvas) {
        int x = mWidth / 2;
        int y = mController.getVersion() == DatePickerDialog.Version.VERSION_1
            ? (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2
            : getMonthHeaderSize() / 2 - MONTH_DAY_LABEL_TEXT_SIZE;
        canvas.drawText(mMonthTitlePaint, getMonthAndYearString(), x, y);
        Utils.log("SimpleMonthView drawMonthTitle text:" + getMonthAndYearString() + ", x:" + x + ", y:" + y + ", totalHeight:" + getHeight());
    }

    protected void drawMonthDayLabels(Canvas canvas) {
        int y = getMonthHeaderSize() - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
        int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);

        for (int i = 0; i < mNumDays; i++) {
            int x = (2 * i + 1) * dayWidthHalf + mEdgePadding;

            int calendarDay = (i + mWeekStart) % mNumDays;
            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
            String weekString = "";
            switch (mController.getCalendarType()) {
                case GREGORIAN:
                    weekString = getWeekDayLabel(mDayLabelCalendar);
                    break;
                case JALALI:
                    weekString = JalaliCalendar.getWeekDayName((mDayLabelCalendar).get(Calendar.DAY_OF_WEEK)).substring(0, 1);
                    break;
            }
            canvas.drawText(mMonthDayLabelPaint, weekString, x, y);
            Utils.log("SimpleMonthView drawMonthDayLabels text:" + weekString + ", x:" + x + ", y:" + y + ", height:" + getHeight());
        }
    }

    /**
     * Draws the week and month day numbers for this week. Override this method
     * if you need different placement.
     *
     * @param canvas The canvas to draw on
     */
    protected void drawMonthNums(Canvas canvas) {
        int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
            + getMonthHeaderSize();
        // TODO: look at the calculations used by the framework picker to properly align this with the buttons
        final int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);
        int j = findDayOffset();
        for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
            final int x = (2 * j + 1) * dayWidthHalf + mEdgePadding;

            int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;

            final int startX = x - dayWidthHalf;
            final int stopX = x + dayWidthHalf;
            final int startY = y - yRelativeToDay;
            final int stopY = startY + mRowHeight;

            drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);

            j++;
            if (j == mNumDays) {
                j = 0;
                y += mRowHeight;
            }
        }
    }

    /**
     * This method should draw the month day.  Implemented by sub-classes to allow customization.
     *
     * @param canvas The canvas to draw on
     * @param year The year of this month day
     * @param month The month of this month day
     * @param day The day number of this month day
     * @param x The default x position to draw the day number
     * @param y The default y position to draw the day number
     * @param startX The left boundary of the day number rect
     * @param stopX The right boundary of the day number rect
     * @param startY The top boundary of the day number rect
     * @param stopY The bottom boundary of the day number rect
     */
    public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
                                      int x, int y, int startX, int stopX, int startY, int stopY);

    /**
     * 日期便宜
     *
     * @return Offset
     */
    protected int findDayOffset() {
        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
            - mWeekStart;
    }

    /**
     * Calculates the day that the given x position is in, accounting for week
     * number. Returns the day or -1 if the position wasn't in a day.
     *
     * @param x The x position of the touch event
     * @param y The y position of the touch event
     * @return The day number, or -1 if the position wasn't in a day
     */
    public int getDayFromLocation(float x, float y) {
        Utils.log("MonthView getDayFromLocation touch position(" + x + ", " + y + ")");

        final int day = getInternalDayFromLocation(x, y);
        Utils.log("MonthView getDayFromLocation day:" + day + ", mNumCells:" + mNumCells);
        if (day < 1 || day > mNumCells) {
            return -1;
        }
        return day;
    }

    /**
     * Calculates the day that the given x position is in, accounting for week
     * number.
     *
     * @param x The x position of the touch event
     * @param y The y position of the touch event
     * @return The day number
     */
    protected int getInternalDayFromLocation(float x, float y) {
        int dayStart = mEdgePadding;
        if (x < dayStart || x > mWidth - mEdgePadding) {
            return -1;
        }
        Utils.log("MonthView getInternalDayFromLocation getMonthHeaderSize:" + getMonthHeaderSize() + ", mRowHeight:" + mRowHeight);
        int row = (int) (y - getMonthHeaderSize()) / mRowHeight;
        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mEdgePadding));
        Utils.log("MonthView getInternalDayFromLocation row:" + row + ", column:" + column);

        int day = column - findDayOffset() + 1;
        day += row * mNumDays;
        return day;
    }

    /**
     * Called when the user clicks on a day. Handles callbacks to the
     * {@link OnDayClickListener} if one is set.
     * <p/>
     * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
     *
     * @param day The day that was clicked
     */
    private void onDayClick(int day) {
        Utils.log("MonthView onDayClick day:" + day);
        // If the min / max date are set, only process the click if it's a valid selection.
        if (mController.isOutOfRange(mYear, mMonth, day)) {
            Utils.log("MonthView onDayClick isOutOfRange");
            return;
        }


        if (mOnDayClickListener != null) {
            mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day, mController.getTimeZone()));
        }
    }

    /**
     * 突出显示
     *
     * @param year as an int
     * @param month as an int
     * @param day as an int
     * @return true if the given date should be highlighted
     */
    protected boolean isHighlighted(int year, int month, int day) {
        return mController.isHighlighted(year, month, day);
    }

    /**
     * Return a 1 or 2 letter String for use as a weekday label
     *
     * @param day The day for which to generate a label
     * @return The weekday label
     */
    private String getWeekDayLabel(Calendar day) {
        Locale locale = mController.getLocale();

//        // Getting the short label is a one liner on API >= 18
        if (weekDayLabelFormatter == null) {
            weekDayLabelFormatter = new SimpleDateFormat("EEEEE", locale);
        }
        return weekDayLabelFormatter.format(day.getTime());
    }

    /**
     * Handles callbacks when the user clicks on a time object.
     */
    public interface OnDayClickListener {
        void onDayClick(MonthView view, CalendarDay day);
    }
}
